﻿/* cpcPaChi;

   Un compilador de Pascal chiquitito para CPC
   Por Nacho Cabanes - Version en C#

   Clase "Scanner" (analizador lexico)

   Versiones hasta la fecha:

   Num.    Fecha       Cambios
   ---------------------------------------------------
   0.15cs  01-Dic-2010  Añadido "ObtenerToken" (que aún no se usa)
                        y ObtenerCadena (para textos entre comillas)
   0.14cs  18-Nov-2010  Renombrado de Lexer a Scanner,
                        más "universal"
   0.11cs  08-Nov-2010  Añadido "Retroceder", para cuando se lee un
                        carácter de más (por ejemplo, al analizar una
                        etiqueta de salto)
   0.09cs  04-Nov-2010  Añadido "ErrorFatal", para interrumpir
                        el análisis cuando hay una incongruencia

   0.08cs  02-Nov-2010  Los identificadores empiezan por letra
                        pero pueden seguir por letra y número
                        Se permiten (y se ignoran) comentarios
                        entre {..} y entre (* .. *)

   0.07cs  31-Oct-2010  Anadido "obtenerEnteroOIdentificador"
                        y "leerCualquierSimbolo"
                        Si "depurando" es "true", se muestran
                        mensajes auxiliares de deporación

   0.06cs  29-Oct-2010  Salta espacios en blanco

   0.03cs  22-Oct-2010  Primera version del analizador lexico:
                        es capaz de devolver un identificador,
                        un entero y de comprobar si el siguiente
                        simbolo es uno prefijado;
                        lee linea a linea del fichero de entrada
*/

using System;
using System.IO;

public class Scanner
{
  StreamReader ficheroOrigen;
  string lineaActual="";
  int posicionLineaActual = 0;
  int numeroLineaActual = 0;
  bool finFicheroEntrada = false;
  bool depurando = false;

  /* Constructor: abre el fichero de entrada */
  public Scanner(string nombreFichero)
  {
    if ( ! File.Exists(nombreFichero) )
      ErrorFatal("Fichero de entrada no encontrado!");
    ficheroOrigen = File.OpenText(nombreFichero);
    leerLinea();
  }

  /* Destructor: cierra el fichero de entrada */
  ~Scanner()
  {
    ficheroOrigen.Close();
  }

  /* Lee una linea del fichero de entrada. Solo para uso interno del lexer */
  void leerLinea()
  {
    numeroLineaActual ++;
    lineaActual = ficheroOrigen.ReadLine();
    if (lineaActual == null)
      finFicheroEntrada = true;
    if (depurando)
      Console.WriteLine("Leída línea: -{0}-", lineaActual);
  }


  /* Obtiene una letra del fichero. Se apoya en leerLinea: toma la
    siguiente letra de "LineaDeEntrada", o lee otra linea del fichero
    si la actual ha terminado */
  public char ObtenerLetra()
  {

    //if ((depurando) && (posicionLineaActual < lineaActual.Length))
    //  Console.Write("L:{0}.", lineaActual[posicionLineaActual]);

    if (posicionLineaActual >= lineaActual.Length)
    {
      leerLinea();
      posicionLineaActual = 0;
      return ' ';
    }
    else
      return lineaActual[posicionLineaActual ++];
  }


  /* Obtiene un identificador: devuelve una secuencia de letras */
  public string ObtenerIdentificador()
  {
    if (finFicheroEntrada)
      return null;

    saltarBlancos();
    string lexema = "";
    char letra = (""+ObtenerLetra()).ToUpper()[0];  // (letra en mayusculas)

    // Un identificador debe empezar por letra
    if ( (letra >= 'A') && (letra <= 'Z') )
    {
      do
      {
          lexema += letra;
          letra = (""+ObtenerLetra()).ToUpper()[0];
      }  // Y puede seguir por letra o cifra numérica
      while  ( ( (letra >= 'A') && (letra <= 'Z') )
        || ( (letra >= '0') && (letra <= '9') ) );
    }
    // He leido uno de mas, asi que retrocedo
    if (posicionLineaActual > 0)
      posicionLineaActual --;

    if (depurando)
      Console.WriteLine("  Identificador: -{0}-", lexema);

    return lexema;
  }

  /* Obtiene una cadena: lee hasta la siguiente comilla */
  public string ObtenerCadena()
  {
    char letra = ObtenerLetra();
    string cadena = "" + letra;
    do
    {
      letra = ObtenerLetra();
      if (letra != '\'')
        cadena += letra;
    }
    while (letra != '\'');
    Retroceder();
    return cadena;
  }


  /* Obtiene un entero: devuelve una secuencia de cifras */
  public int ObtenerEntero()
  {
    saltarBlancos();
    string lexema = "";
    char letra = ObtenerLetra();

    while ( (letra >= '0') && (letra <= '9') )
    {
        lexema += letra;
        letra = ObtenerLetra();
    }
    // He leido uno de mas, asi que retrocedo
    if (posicionLineaActual > 0)
      posicionLineaActual --;

    if (depurando)
      Console.WriteLine("  Entero: -{0}-", lexema);

    return Convert.ToInt32( lexema );
  }


  /* Obtiene un entero o un identificador, según lo que sea
     el primero símbolo */
  public string ObtenerEnteroOIdentificador()
  {
    // Tanteo el siguiente simbolo
    char letra = LeerCualquierSimbolo();
    // Retrocedo una posicion
    posicionLineaActual --;
    // Y actuo en consecuencia
    if ( (letra >= '0') && (letra <= '9') )
      return Convert.ToString( ObtenerEntero() );
    else
      return ObtenerIdentificador();
  }

  /* Lee un símbolo del fichero de entrada y termina con
  un mensaje de error si encuentra otro distinto al que esperaba */
  public void LeerSimbolo(char simboloEsperado)
  {
    char letra;

    saltarBlancos();
    letra = ObtenerLetra();
    if (letra != simboloEsperado)
      ErrorFatal(
        "Se esperaba " + simboloEsperado +
        " y se ha encontrado " + letra);

    if (depurando)
      Console.WriteLine("  Emparejado: -{0}-", simboloEsperado);
  }


  /* Lee cualquier símbolo del fichero de entrada, saltando
     primero los blancos */
  public char LeerCualquierSimbolo()
  {
    saltarBlancos();
    char letra = ObtenerLetra();
    //if (depurando)
    //  writeln('Simbolo: ', letra);
    return letra;
  }


  /* Lee cualquier símbolo del fichero de entrada, saltando
     primero los blancos */
  public Token ObtenerToken()
  {
    saltarBlancos();
    int fila = numeroLineaActual;
    int columna = posicionLineaActual;
    string lexema = "";
    tToken tipo = tToken.ID;

    char letra = ObtenerLetra();
    if ((letra >='0') && (letra <= '9'))
    {
            tipo = tToken.ENTERO;
            Retroceder();
            lexema = ObtenerEntero().ToString();
    }
    else if ( ((letra >='a') && (letra <= 'z'))
                 || ((letra >='A') && (letra <= 'Z')) )
    {
            tipo = tToken.ID;
            Retroceder();
            lexema = ObtenerIdentificador();
    }
    else switch(letra)
    {
      case '(': lexema = ""+letra; tipo = tToken.PARI; break;
      case ')': lexema = ""+letra; tipo = tToken.PARD; break;
      case ';': lexema = ""+letra; tipo = tToken.PYC; break;
      case ':':
            char letra2 = ObtenerLetra();
            if (letra2 == '=')
            {
                lexema = ":="; tipo = tToken.ASIGN; break;
            }
            else
            {
                lexema = ""+letra; tipo = tToken.DOSP; break;
            }
      case '=': lexema = ""+letra; tipo = tToken.IGUAL; break;
      case ',': lexema = ""+letra; tipo = tToken.COMA; break;
      case '.': lexema = ""+letra; tipo = tToken.PUNTO; break;
      case '>':
            letra2 = ObtenerLetra();
            if (letra2 == '=')
            {
                lexema = ">="; tipo = tToken.MAYORIG; break;
            }
            else
            {
                lexema = ""+letra; tipo = tToken.MAYOR; break;
            }
      case '<':
            letra2 = ObtenerLetra();
            if (letra2 == '=')
            {
                lexema = "<="; tipo = tToken.MENORIG; break;
            }
            else if (letra2 == '>')
            {
                lexema = "<>"; tipo = tToken.DISTINT; break;
            }
            else
            {
                lexema = ""+letra; tipo = tToken.MENOR; break;
            }
      default: ErrorFatal("Carácter no permitido"); break;
    }
    return new Token(tipo, lexema, fila, columna);
  }


  void saltarBlancos()
  {
    if (depurando)
        Console.WriteLine("  Saltando blancos...");

    // Leo el siguiente símbolo y repito mientras sea un blanco
    char proximaLetra = ObtenerLetra();

    while ((proximaLetra == ' ') || (proximaLetra == '\n') ||
          (proximaLetra == '\r') ||  (proximaLetra == '\t') )
      proximaLetra = ObtenerLetra();


    // Salto comentarios entre llaves
    if ( proximaLetra == '{')
    {
      if (depurando)
        Console.WriteLine("  Comentario con llave");
      do
        proximaLetra = ObtenerLetra();
      while ( proximaLetra != '}');

      proximaLetra = ObtenerLetra(); // Salto la llave de cierre
      saltarBlancos();  // Puede haber más blancos detrás
    }


    // Salto comentarios con paréntesis-asterisco
    if ( ( proximaLetra == '(')
     && (posicionLineaActual < lineaActual.Length)
     && (lineaActual[posicionLineaActual] == '*') )
    {
      if (depurando)
        Console.WriteLine("  Comentario con paréntesis-asterisco");
      do
        proximaLetra = ObtenerLetra();
      while ( ( proximaLetra != '*')
        || ( (posicionLineaActual < lineaActual.Length-1)
        && (lineaActual[posicionLineaActual] != ')')) );

      proximaLetra = ObtenerLetra(); // Salto el asterisco
      proximaLetra = ObtenerLetra(); // Salto el paréntesis de cierre
      saltarBlancos();  // Puede haber más blancos detrás
    }

    // Finalmente he leido uno de mas, asi que retrocedo
    if (posicionLineaActual > 0)
      posicionLineaActual --;
  }


  // Para cuando se lee de más; por ejemplo, al analizar LABEL
  public void Retroceder()
  {
    posicionLineaActual -- ;
  }

  public void ErrorFatal(string mensaje)
  {
    Console.Write("Linea: {0}, Pos: {1}. ",
        numeroLineaActual, posicionLineaActual+1);
    Console.WriteLine(mensaje);
    Environment.Exit(1);
  }


}
